package wei.qiang.ye.springsecuritydemo.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import wei.qiang.ye.springsecuritydemo.common.MySessionExpiredStrategy;
import wei.qiang.ye.springsecuritydemo.filter.SmsCodeFilter;
import wei.qiang.ye.springsecuritydemo.filter.ValidateCodeFilter;
import wei.qiang.ye.springsecuritydemo.handler.MyAuthenticationAccessDeniedHandler;
import wei.qiang.ye.springsecuritydemo.handler.MyAuthenticationFailureHandler;
import wei.qiang.ye.springsecuritydemo.handler.MyAuthenticationSucessHandler;
import wei.qiang.ye.springsecuritydemo.service.UserDetailService;

import javax.sql.DataSource;

@EnableGlobalMethodSecurity(prePostEnabled = true) //开启权限控制注解使用
@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private MyAuthenticationSucessHandler authenticationSucessHandler;
    @Autowired
    private MyAuthenticationFailureHandler authenticationFailureHandler;
    @Autowired
    private ValidateCodeFilter validateCodeFilter;
    @Autowired
    private DataSource dataSource;
    @Autowired
    private UserDetailService userDetailService;

    @Autowired
    private SmsCodeFilter smsCodeFilter;
    @Autowired
    private SmsAuthenticationConfig smsAuthenticationConfig;
    @Autowired
    private MySessionExpiredStrategy sessionExpiredStrategy;
    @Autowired
    private MyAuthenticationAccessDeniedHandler authenticationAccessDeniedHandler;



    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //将ValidateCodeFilter验证码校验过滤器添加到了UsernamePasswordAuthenticationFilter前面
        http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(smsCodeFilter, UsernamePasswordAuthenticationFilter.class) //添加短信验证码过滤器
                .formLogin() // 基于表单认证
//                .loginPage("/login.html") //指定了跳转到登录页面的请求URL
                .loginPage("/authentication/require") // 登录跳转 URL
                .loginProcessingUrl("/login") //对应登录页面form表单的action="/login"
                .successHandler(authenticationSucessHandler)
                .failureHandler(authenticationFailureHandler)
                .and()
                .rememberMe()
                .tokenRepository(persistentTokenRepository()) // 配置 token 持久化仓库
                .tokenValiditySeconds(3600) // remember 过期时间，单为秒
                .userDetailsService(userDetailService) //处理通过token对象自动登录
                .and()
                .authorizeRequests() // 授权配置
                .antMatchers("/login.html",
                        "/authentication/require",
                        "/code/image","/code/sms"
                        ,"/session/invalid","/signout/success").permitAll()  //跳转到登录页面的请求不被拦截，否则会进入无限循环
                .anyRequest()  // 所有请求
                .authenticated() // 都需要认证
                .and()
                .csrf().disable()
                //// 将短信验证码认证配置加到 Spring Security 中
                .apply(smsAuthenticationConfig)
                .and()
                .logout()
                .logoutUrl("/signout")  //退出登录的URL为/signout
                .logoutSuccessUrl("/signout/success") //退出成功后跳转的URL为/signout/success
                .deleteCookies("JSESSIONID") //删除名称为JSESSIONID的cookie
                .and()
                .sessionManagement() // 添加 Session管理器
                .invalidSessionUrl("/session/invalid") // Session失效后跳转到这个链接
                .maximumSessions(1) //最大Session并发数量
                .expiredSessionStrategy(sessionExpiredStrategy); //Session在并发下失效后的处理策略

        http.exceptionHandling()
                .accessDeniedHandler(authenticationAccessDeniedHandler); //处理权限不足的情况
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * token持久化对象
     * PersistentTokenRepository为一个接口类，这里我们用的是数据库持久化，所以实例用的是PersistentTokenRepository的实现类JdbcTokenRepositoryImpl
     * @return
     */
    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        //createTableOnStartup属性用于是否启动项目时创建保存token信息的数据表
        jdbcTokenRepository.setCreateTableOnStartup(false);
        return jdbcTokenRepository;
    }
}
